/**
 * Created with CLion.
 * Description:
 * User: loong
 * Date: 2024/3/8
 * Time: 22:23
 */

#include "PluginProperty.h"
#include "algorithm.h"


namespace CES::Internal {
    QPluginLoader PluginProperty::pluginLoader;
    const QRegularExpression PluginSpec::versionRegExp_("^([0-9]+)(?:[.]([0-9]+))?(?:[.]([0-9]+))?(?:_([0-9]+))?$");

    bool PluginDepend::operator==(const PluginDepend &other) const {
        return std::tie(this->type_, this->name_, this->version_) == std::tie(other.type_, other.name_, other.version_);
    }

    bool PluginSpec::IsValidVersion(const QString &_version) {
        return versionRegExp_.match(_version).hasMatch();
    }

    int PluginSpec::VersionCompare(const QString &_version1, const QString &_version2) {
        const QRegularExpressionMatch match1 = versionRegExp_.match(_version1);
        const QRegularExpressionMatch match2 = versionRegExp_.match(_version2);
        if (!match1.hasMatch())
            return 0;
        if (!match2.hasMatch())
            return 0;
        for (int i = 0; i < 4; ++i) {
            const int number1 = match1.captured(i + 1).toInt();
            const int number2 = match2.captured(i + 1).toInt();
            if (number1 < number2)
                return -1;
            if (number1 > number2)
                return 1;
        }
        return 0;
    }

    PluginPropertySPtr PluginProperty::ReadFile(const QString &_path) {
        QPluginLoader loader(_path);

        auto ret = ReadMetaData(loader.metaData());
        if (nullptr != ret)
            ret->pluginFilePath_ = _path;

        return ret;
    }

    bool PluginProperty::Provides(const QString &_pluginName, const QString &_pluginVersion) const {
        if (_pluginName.compare(pluginSpec_->name_) != 0)
            return false;
        return PluginSpec::VersionCompare(pluginSpec_->version_, _pluginVersion) >= 0;
    }

    bool PluginProperty::ResolveDepends(const QVector<PluginPropertySPtr> &_specs) {
        if (hasError)
            return false;

        QVector<PluginPropertySPtr> depends;
        for (auto depend: pluginSpec_->depends_) {
            auto found = Utils::findOrDefault(_specs, [=](const PluginPropertySPtr &spec) {
                return spec->Provides(depend->name_, depend->version_);
            });

            if (nullptr == found) {
                if (depend->type_ == PluginDepend::Required) {
                    hasError = true;
                    if (!errorString.isEmpty())
                        errorString.append("\n");
                    errorString.append(
                        QString("Could not resolve dependency '%1(%2)'").arg(depend->name_).arg(depend->version_));
                }
                continue;
            }

            depends.append(found);
        }
        pluginDepends_ = depends;

        return hasError;
    }

    bool PluginProperty::LoadLibrary() {
        if (hasError)
            return false;

        pluginLoader.setFileName(this->pluginFilePath_);
        if (!pluginLoader.load()) {
            hasError = true;
            errorString = pluginLoader.errorString();
            return false;
        }

        auto pluginObject = qobject_cast<Core::IPlugin *>(pluginLoader.instance());
        if (nullptr == pluginObject) {
            hasError = true;
            errorString = "Plugin is not valid (does not derive from IPlugin)";
            pluginLoader.unload();
            return false;
        }

        pluginSPtr_ = std::shared_ptr<Core::IPlugin>(pluginObject);
        return true;
    }

    bool PluginProperty::InitPlugin(const Core::IContextSPtr &_content) {
        if (hasError)
            return false;

        if (nullptr == pluginSPtr_) {
            errorString = "Internal error: have no plugin instance to initialize";
            hasError = true;
            return false;
        }

        pluginSPtr_->InitPlugin(_content);

        return true;
    }

    bool PluginProperty::StartPlugin() {
        if (hasError)
            return false;

        if (nullptr == pluginSPtr_) {
            errorString = "Internal error: have no plugin instance to perform StartPlugin";
            hasError = true;
            return false;
        }

        pluginSPtr_->StartPlugin();
        return true;
    }

    PluginPropertySPtr PluginProperty::ReadMetaData(const QJsonObject &pluginMetaData) {
        QJsonValue value = pluginMetaData.value(QLatin1String("IID"));
        if (!value.isString())
            return nullptr;

        value = pluginMetaData.value("MetaData");
        if (!value.isObject())
            return nullptr;

        auto ret = std::make_shared<PluginProperty>();
        ret->pluginSpec_ = std::make_shared<PluginSpec>();
        ret->pluginDepends_.clear();
        ret->errorString.clear();

        auto metaData = value.toObject();
        ret->pluginSpec_->name_ = metaData.value("Name").toString();
        ret->pluginSpec_->version_ = metaData.value("Version").toString();
        ret->pluginSpec_->category_ = metaData.value("Category").toString();
        ret->pluginSpec_->vendor_ = metaData.value("Vendor").toString();
        ret->pluginSpec_->description_ = metaData.value("Description").toString();

        value = metaData.value("Dependencies");

        const QJsonArray array = value.toArray();
        for (auto info: array) {
            auto obj = info.toObject();

            auto dep = std::make_shared<PluginDepend>();
            dep->name_ = obj.value("Name").toString();
            dep->version_ = obj.value("Version").toString();

            value = obj.value("Type").toString();
            dep->type_ = value == "Optional" ? PluginDepend::Optional : PluginDepend::Required;

            ret->pluginSpec_->depends_.push_back(dep);
        }
        return ret;
    }

    bool PluginProperty::UnInitPlugin() {
        pluginSPtr_->DeInitPlugin();
        return false;
    }

    bool PluginProperty::StopPlugin() {
        pluginSPtr_->StopPlugin();
        return false;
    }
} // namespace CES::Internal
